/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package server;

import java.io.*;
import java.net.*;
import java.security.MessageDigest;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.sun.org.apache.xerces.internal.impl.dv.util.*;

// Klientske vlakno pro daemona
public class DaemonClientThread extends Thread
{
	private Socket socket; // promenna pro socket
	private BufferedOutputStream bout = null; // promenna slouzici k odesilani dat
	private BufferedInputStream bin = null; // promenna slouzici ke cteni dat

	public DaemonClientThread(Socket socket)
	{
		this.socket = socket; // ulozeni socketu, na kterem je klient pripojen
		try
		{
			bin = new BufferedInputStream(socket.getInputStream()); // navazani spojeni s prijmacim
																	// kanalem
			bout = new BufferedOutputStream(socket.getOutputStream()); // navazani spojeni s
																		// odesilacim kanalem
		}
		catch (Exception e)
		{
			e.printStackTrace(); // vypis vyjimky do konzole
			close(); // Uzavreni klientskeho vlakna po chybe
		}
	}

	// Cteni prikazu posilane daemonem
	@Override
	public void run()
	{
		sendPhrase();
		try
		{
			while (!isInterrupted()) // opakovat dokud neni vlakno preruseno
			{
				String data = Sifrovani.decrypt(readData()); // cekani na prijeti dat
				if (data == null) // pokud doslo k chybe a nebyla prijata zadna data
					continue; // preskoc na konec cyklu

				int opcode = Integer.parseInt(data.substring(2, 4)); // Rozdeleni Kodu akce
				String message = data.substring(4); // a samotne zpravy

				switch (opcode)
				// podle typu se provede prislusna akce
				{
					case 3: // restartovani server
						Main.restart(true); // zavola metodu ve tride Main, ktera restartuje server
						break;
					case 4: // vypnuti serveru
						Main.restart(false); // zavola metodu ve tride Main, ktera vypne server
						break;
					case 5: // kontrola hesla pri pripojeni
						if (!checkPassword(message)) // pokud je heslo spatne
						{
							writeData(Sifrovani.encrypt("0x06")); // informuje o tom daemon clienta
							close(); // odpoji ho
							interrupt(); // a vypne jeho klientske vlakno
						}
						break;
				}
			}
		}
		catch (Exception e)
		{
		}
		finally
		// at uz metoda vyhodi vyjimku nebo ne
		{
			close(); // klient je odpojen a vlakno zruseno
		}
	}

	// Kontrola hesla
	private boolean checkPassword(String message)
	{
		Connection c = null; // promenna, ktera obsahuje pripojeni do MySQL databaze
		ResultSet rs = null; // promenna, do ktere se ulozi vysledek SQL prikazu
		try
		{
			c = Mysql.getInstance().getConnection(); // vytvore pripojeni do db
			rs = c.prepareStatement("SELECT * FROM passwords").executeQuery(); // priprava a
																				// spusteni sql
																				// prikazu

			MessageDigest md = MessageDigest.getInstance("SHA"); // priprava message digest
																	// algoritmu
			byte[] newpass = message.getBytes("UTF-8"); // rozdeleni hesla na byte pole podle
														// kodovani utf-8
			newpass = md.digest(newpass); // vytvoreni hashe
			String encpwd = Base64.encode(newpass); // zakodovani hashe

			while (rs.next()) // dokud jsou ve vysledku hodnoty (radky z tabulky)
			{
				String p = rs.getString("password"); // vyber hodnotu ze sloupecku "password"
				if (p.equals(encpwd)) // pokud hodnota odpovida zadanemu heslu
					return true; // vrati se true
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
			try
			{
				rs.close(); // zavrit vysledek sql prikazu
				c.close();
			}
			catch (SQLException e)
			{
				e.printStackTrace();
			}
		}
		return false; // pokud program dojde sem pak heslo neni dobre
	}

/*	private void createPassword(String password)
	{
		Connection c = null;
		PreparedStatement statement = null;
		try
		{
			MessageDigest md = MessageDigest.getInstance("SHA");
			byte[] newpass = password.getBytes("UTF-8");
			newpass = md.digest(newpass);
			String encpwd = Base64.encode(newpass);

			c = Mysql.getInstance().getConnection();
			statement = c.prepareStatement("INSERT INTO password VALUES (?)");
			statement.setString(1, encpwd);
			statement.execute();

		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
			try
			{
				statement.close();
				c.close();
			}
			catch (SQLException e)
			{
				e.printStackTrace();
			}
		}

	}*/

	// synchronizace sifrovaciho klice Vernamovou sifrou
	private void sendPhrase()
	{
		try
		{
			Thread.sleep(100); // uspani vlakna na 100ms
			// 1st pass
			String phrase = Sifrovani.getPhrase(); // zjisteni vygenerovaneho sifrovaciho klice
			Vernam v = new Vernam(); // vytvoreni nove instance vernamovy sifry
			v.setPhrase(phrase); // nastaveni sifrovaciho klice do teto instance
			v.generateKey(); // vygenerovani nahodneho klice, kterym bude sirovaci klic zasifrovan
			writeData(v.XOR()); // provedeni XOR mezi sifrovacim klicem a docasnym klicem a odeslani
								// do clienta
			Thread.sleep(100); // uspani vlakna na 100ms
			// 2nd pass
			String b = readData(); // cteni zpravy od klienta (ta je zasifrovana vygenerovanym
									// klicem klienta)
			Thread.sleep(100); // uspani vlakna na 100ms
			v.setPhrase(b); // nastaveni prijmute zpravy do instance sifry
			writeData(v.XOR()); // odsifrovani a odeslani (tato zprava je ted zasifrovana pouze
								// klicem klienta)
			Thread.sleep(100); // uspani vlakna na 100ms
			v.forget(); // vymazani zpravy a docasneho klice z pameti
		}
		catch (IOException e)
		{
		}
		catch (Exception e)
		{
			e.printStackTrace(); // vypsani chyby
		}
	}

	// Posila data pres sit ke klientovi
	private void writeData(String s) throws IOException
	{
		byte[] msg = s.getBytes(); // rozdeleni zpravy na pole typu byte
		int len = msg.length + 2; // zjisteni velikosti zpravy
		bout.write(len & 0xff); // odeslani velikosti zpravy
		bout.write(len >> 8 & 0xff); // aby klient vedel jak dlouho ma cist a zda mu prisla zprava
										// cela
		bout.write(msg); // odeslani samotne zpravy
		bout.flush(); // uvolneni odesilaci pameti
	}

	// Prijma data pres sit od klienta
	private String readData() throws IOException
	{
		int lengthLo = bin.read(); // cteni spodnich bitu delky
		int lengthHi = bin.read(); // cteni hornich bitu delky
		int length = lengthHi * 256 + lengthLo; // vypocet celkove delky

		byte[] data = new byte[length - 2]; // vytvoreni docasneho pole, kam se bude zprava ukladat

		int receivedBytes = 0; // pocet prenesenych dat
		int newBytes = 0; // cislo prenasenych dat (-1 pokud dosahne konce streamu)
		int left = length - 2; // zbyvajici pocet dat
		while (newBytes != -1 && receivedBytes < length - 2) // dokud cteni nedostahne konce streamu
		{
			// cteni dat. metoda read je rekurzivni, proto se uklada do vstupniho pole, vraci
			// poradove cislo
			newBytes = bin.read(data, receivedBytes, left); // prectenych dat
			receivedBytes = receivedBytes + newBytes; // ulozeni noveho poctu prenesenych dat
			left -= newBytes; // ulozeni noveho poctu zbyvajicich dat
		}
		return new String(data); // prevod zpravy do Stringu a navraceni
	}

	// Metoda pro odpojeni klienta
	public void close()
	{
		try
		{
			bout.close(); // uzavreni odesilaciho kanalu
			bin.close(); // uzavreni prijmaciho kanalu
			socket.close(); // uzavreni socketu
		}
		catch (IOException e)
		{
		}
		interrupt(); // ukonceni vlakna
	}
}